package standard

import (
	"bytes"
	"go-forth/pkg/forth"
	"io"
	"strconv"
	"strings"
)

const (
	vTrue          = -1
	vFalse         = 0
	lineTerminator = 10

	VarNumericBuffer = "_pictured_numeric_output_string_buffer_offset"
)

var unknownWord string

func Word(vm forth.StackVM) {
	wordBuffer := vm.Input().Buffer()
	var buf []byte
	for {
		b, err := wordBuffer.ReadByte()
		if err != nil && err != io.EOF {
			panic(err)
		}

		if err == io.EOF {
			// try refill and read again
			if vm.Input().Refill() {
				continue
			}
			// process the last word
			if len(buf) > 0 {
				break
			}
			// otherwise stop at EOF
			panic("EOF")
		}

		if b == ' ' || b == '\n' || b == '\t' || b == '\r' { // delimiter
			if len(buf) == 0 {
				continue // keep reading until EOF or anything other than delimiter
			}
			break
		}

		buf = append(buf, b)
	}

	w := string(buf)
	vm.Push(w) // equivalent to c-addr
}

func Find(vm forth.StackVM) {
	name := vm.Pop().(string)
	xt, immediate, found := vm.Find(name)
	if !found {
		unknownWord = name
		vm.Push(name)
		vm.Push(0)
	} else {
		vm.Push(xt)
		if immediate {
			vm.Push(1)
		} else {
			vm.Push(-1)
		}
	}
}

func Create(vm forth.StackVM) {
	Word(vm) // read string from input
	here := vm.DataSpace().Reserved()
	vm.Register(vm.Pop().(string), func(vm2 forth.StackVM) {
		vm2.Push(here)
	}) // create the definition
}

func ToBody(vm forth.StackVM) {
	xt := vm.Pop().(forth.ExecutionToken)
	vm.Push(xt.Location())
}

func Comma(vm forth.StackVM) {
	// TODO COLON should be rewritten using Comma
	x := vm.Pop()
	here := vm.DataSpace().Reserved()
	vm.DataSpace().Reserve(1)
	vm.DataSpace().Store(here, x)
}

func Lbrac(vm forth.StackVM) {
	vm.Push(vFalse)
	vm.Push(0) // STATE
	Store(vm)
}

func Rbrac(vm forth.StackVM) {
	vm.Push(vTrue)
	vm.Push(0) // STATE
	Store(vm)
}

func Colon(vm forth.StackVM) {
	Word(vm)                       // read string from input
	vm.Register(vm.Pop().(string)) // create the definition
	Rbrac(vm)                      // enable compilation
}

func FromR(vm forth.StackVM) {
	vm.Push(vm.ReturnStack().Pop())
}

func ToR(vm forth.StackVM) {
	vm.ReturnStack().Push(vm.Pop())
}

func Execute(vm forth.StackVM) {
	forth.TryExecute(vm.Pop(), vm)
}

func Tick(vm forth.StackVM) {
	Word(vm)
	Find(vm)
	ret := vm.Pop()
	if ret == 0 {
		panic("ambiguous condition: " + unknownWord)
	}
	// xt already on stack
}

func BracketTick(vm forth.StackVM) {
	Word(vm)
	Find(vm)
	ret := vm.Pop()
	if ret == 0 {
		panic("ambiguous condition")
	}
	xt := vm.Pop()
	vm.Append(func(vm2 forth.StackVM) {
		vm2.Push(xt)
	})
}

func Literal(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("ambiguous condition")
	}
	x := vm.Pop()
	vm.Append(func(vm2 forth.StackVM) {
		vm2.Push(x)
	})
}

func Postpone(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("ambiguous condition")
	}
	Word(vm)
	Find(vm)
	ret := vm.Pop()
	if ret == 0 {
		panic("ambiguous condition")
	}
	xt := vm.Pop().(forth.ExecutionToken)
	if ret == 1 { // direct
		vm.Append(func(vm2 forth.StackVM) {
			xt.RunOn(vm2)
		})
	} else { // indirect
		vm.Append(func(vm2 forth.StackVM) {
			vm2.Append(xt)
		})
	}
}

func Here(vm forth.StackVM) {
	vm.Push(vm.DataSpace().Reserved())
}

func Fetch(vm forth.StackVM) {
	addr := vm.Pop().(int)
	vm.Push(vm.DataSpace().Fetch(addr))
}

func Store(vm forth.StackVM) {
	addr := vm.Pop().(int)
	vm.DataSpace().Store(addr, vm.Pop())
}

func Allot(vm forth.StackVM) {
	n := vm.Pop().(int)
	vm.DataSpace().Reserve(n)
}

func Interpret(vm forth.StackVM) {
	// The logic can also be implemented using pure Forth words. But before a standard set of words are defined,
	// we need to implement them in Go.
	// : INTERPRET					( - )
	// WORD FIND					( - c-addr 0 | xt 1 | -1 )
	// DUP 0= IF					( c-addr 0 - c-addr 0 )
	//   DROP NUMBER				( c-addr 0 - n )
	//   STATE @ IF					\ is compiling
	//   ' LIT COMPILE,				\ compile LIT
	//   COMPILE,					\ compile n
	// ELSE 1= IF EXECUTE			\ execute xt if immediate
	//   ELSE STATE @ IF EXECUTE	\ execute xt if compiling
	//     ELSE COMPILE,			\ compile xt
	//     THEN
	//   THEN
	// THEN
	Word(vm)
	Find(vm)
	retc := vm.Pop()
	if retc == 0 { // not found, try literal
		doNumber(vm)
		n := vm.Pop().(int)
		vm.Push(0) // STATE
		Fetch(vm)
		if vm.Pop() != vFalse {
			// compile LIT
			vm.Append(func(vm forth.StackVM) {
				vm.Delay(func(next forth.Token) {
					vm.Push(next)
				})
			})
			// compile n
			vm.Append(n)
		} else {
			vm.Push(n)
		}
	} else { // found
		xt := vm.Pop().(forth.ExecutionToken)
		vm.Push(0) // STATE
		Fetch(vm)
		if vm.Pop() == vFalse || retc == 1 { // execute directly
			xt.RunOn(vm)
		} else { // compile
			vm.Push(xt)
			CompileComma(vm)
		}
	}
}

func doNumber(vm forth.StackVM) {
	s := vm.Pop().(string)
	i, err := strconv.Atoi(s)
	if err != nil {
		panic(err)
	}
	vm.Push(i)
}

func Quit(vm forth.StackVM) {
	for {
		Interpret(vm)
	}
}

func Emit(vm forth.StackVM) {
	v := byte(vm.Pop().(int))
	_, err := vm.Output().Write([]byte{v})
	if err != nil {
		panic(err)
	}
}

func AbortQuote(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined behavior")
	}
	ccc := readTill(vm, "\"")
	vm.Append(func(vm2 forth.StackVM) {
		flag := vm2.Pop().(int)
		if flag != 0 {
			_, _ = vm2.Output().Write([]byte(ccc))
			vm2.Abort()
		}
	})
}

func readTill(vm forth.StackVM, delim string) string {
	var parts []string
	for {
		Word(vm)
		w := vm.Pop().(string)
		parts = append(parts, w)
		if strings.HasSuffix(w, delim) {
			parts[len(parts)-1] = w[:len(w)-len(delim)]
			break
		}
	}
	return strings.Join(parts, " ")
}

func Abs(vm forth.StackVM) {
	n := vm.Pop().(int)
	if n < 0 {
		n = -n
	}
	vm.Push(n)
}

func Paren(vm forth.StackVM) {
	for {
		Word(vm)
		w := vm.Pop().(string)
		if strings.HasSuffix(w, ")") {
			break
		}
	}
}

func Plus(vm forth.StackVM) {
	vm.Push(vm.Pop().(int) + vm.Pop().(int))
}

func Minus(vm forth.StackVM) {
	vm.Push(-vm.Pop().(int) + vm.Pop().(int))
}

func Multiply(vm forth.StackVM) {
	vm.Push(vm.Pop().(int) * vm.Pop().(int))
}

func Divide(vm forth.StackVM) {
	d := vm.Pop().(int)
	n := vm.Pop().(int)
	vm.Push(n / d)
}

func TwoDupe(vm forth.StackVM) {
	x2 := vm.Pop()
	x1 := vm.Pop()
	vm.Push(x1)
	vm.Push(x2)
	vm.Push(x1)
	vm.Push(x2)
}

func CellPlus(vm forth.StackVM) {
	vm.Push(vm.Pop().(int) + 1)
}

func NoOp(_ forth.StackVM) {}

func True(vm forth.StackVM) {
	vm.Push(vTrue)
}

func False(vm forth.StackVM) {
	vm.Push(vFalse)
}

func And(vm forth.StackVM) {
	vm.Push(vm.Pop().(int) & vm.Pop().(int))
}

func Or(vm forth.StackVM) {
	vm.Push(vm.Pop().(int) | vm.Pop().(int))
}

func Xor(vm forth.StackVM) {
	vm.Push(vm.Pop().(int) ^ vm.Pop().(int))
}

func GreaterThan(vm forth.StackVM) {
	n2 := vm.Pop().(int)
	n1 := vm.Pop().(int)
	if n1 > n2 {
		vm.Push(vTrue)
	} else {
		vm.Push(vFalse)
	}
}

func LessThan(vm forth.StackVM) {
	n2 := vm.Pop().(int)
	n1 := vm.Pop().(int)
	if n1 < n2 {
		vm.Push(vTrue)
	} else {
		vm.Push(vFalse)
	}
}

func Equals(vm forth.StackVM) {
	n2 := vm.Pop().(int)
	n1 := vm.Pop().(int)
	if n1 == n2 {
		vm.Push(vTrue)
	} else {
		vm.Push(vFalse)
	}
}

func NotEquals(vm forth.StackVM) {
	n2 := vm.Pop().(int)
	n1 := vm.Pop().(int)
	if n1 != n2 {
		vm.Push(vTrue)
	} else {
		vm.Push(vFalse)
	}
}

func Key(vm forth.StackVM) {
	r, _, err := vm.Input().Buffer().ReadRune()
	if err != nil && err != io.EOF {
		panic(err)
	}

	if err == io.EOF {
		panic("EOF")
	}

	vm.Push(int(r))
}

func Accept(vm forth.StackVM) {
	n := vm.Pop().(int)
	addr := vm.Pop().(int)
	for i := 0; i < n; i++ {
		Key(vm)
		ch := vm.Pop()
		if ch == lineTerminator {
			vm.Push(i)
			return
		}
		vm.DataSpace().Store(addr+i, ch)
	}
	vm.Push(n)
}

func LessNumberSign(vm forth.StackVM) {
	here := vm.DataSpace().Reserved()
	updateVariable(vm, VarNumericBuffer, here)
}

func NumberSign(vm forth.StackVM) {
	base := readVariable(vm, "BASE").(int)
	pos := readVariable(vm, VarNumericBuffer).(int)

	d2 := vm.Pop().(int)
	if d2 != 0 {
		panic("double-cell integer is 32-bit by standard, so high cell number must be zero")
	}
	d1 := vm.Pop().(int)
	r := d1 % base
	vm.Push(d1 / base) // quotient
	vm.Push(0)

	vm.Push(r)   // remainder
	vm.Push(pos) // location
	Store(vm)    // save digit

	updateVariable(vm, VarNumericBuffer, pos+1)
}

func NumberSignS(vm forth.StackVM) {
	pos := readVariable(vm, VarNumericBuffer).(int)
	if pos <= 0 {
		panic("ambiguous condition")
	}
	for {
		NumberSign(vm)
		TwoDupe(vm)
		d2 := vm.Pop().(int)
		d1 := vm.Pop().(int)
		if d1 == 0 && d2 == 0 {
			break
		}
	}
}

func Sign(vm forth.StackVM) {
	pos := readVariable(vm, VarNumericBuffer).(int)
	if pos <= 0 {
		panic("ambiguous condition")
	}
	n := vm.Pop().(int)
	if n < 0 {
		vm.Push("-")
		vm.Push(pos)
		Store(vm)
	}
}

func NumberSignGreater(vm forth.StackVM) {
	vm.Pop()
	vm.Pop()
	pos := readVariable(vm, VarNumericBuffer).(int)
	here := vm.DataSpace().Reserved()
	length := pos - here
	var buf bytes.Buffer
	for i := length - 1; i >= 0; i-- {
		buf.WriteString(strconv.Itoa(vm.DataSpace().Fetch(here + i).(int)))
	}
	s := buf.String()
	vm.Push(s)
	vm.Push(len(s))
	updateVariable(vm, VarNumericBuffer, 0)
}

func Begin(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined")
	}
	next := vm.DataSpace().Reserved()
	vm.ControlStack().Push(next)
}

func Until(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined")
	}
	dest := vm.ControlStack().Pop().(int)
	vm.Append(func(vm2 forth.StackVM) {
		if x := vm2.Pop(); x == vFalse {
			vm.Goto(dest)
		}
	})
}

func Dupe(vm forth.StackVM) {
	x := vm.Pop()
	vm.Push(x)
	vm.Push(x)
}

func Backslash(vm forth.StackVM) {
	for {
		Key(vm)
		if vm.Pop() == lineTerminator {
			break
		}
	}
}

func While(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined")
	}
	orig := vm.DataSpace().Reserved()
	vm.Append(0) // placeholder
	dest := vm.ControlStack().Pop()
	vm.ControlStack().Push(orig)
	vm.ControlStack().Push(dest)
	vm.Append(func(vm2 forth.StackVM) {
		origOnStack := vm2.Pop().(int)
		if x := vm2.Pop(); x == vFalse {
			vm2.Goto(origOnStack)
		}
	})
}

func Repeat(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined")
	}
	dest := vm.ControlStack().Pop().(int)
	orig := vm.ControlStack().Pop().(int)
	vm.Append(func(vm2 forth.StackVM) {
		vm2.Goto(dest)
	})
	next := vm.DataSpace().Reserved()
	vm.DataSpace().Store(orig, next)
}

func Drop(vm forth.StackVM) {
	vm.Pop()
}

func Type(vm forth.StackVM) {
	s := getString(vm)
	_, _ = vm.Output().Write([]byte(s))
}

func getString(vm forth.StackVM) string {
	u := vm.Pop().(int)
	v := vm.Pop()
	if s, ok := v.(string); ok {
		return s
	}
	// string is stored in data space
	loc := v.(int)
	var buf bytes.Buffer
	for i := 0; i < u; i++ {
		buf.WriteRune(vm.DataSpace().Fetch(loc + i).(rune))
	}
	return buf.String()
}

func DotQuote(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined")
	}
	ccc := readTill(vm, "\"")
	vm.Append(func(vm2 forth.StackVM) {
		vm2.Push(ccc)
		vm2.Push(len(ccc))
		Type(vm)
	})
}

func SQuote(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined")
	}
	ccc := readTill(vm, "\"")
	vm.Append(func(vm2 forth.StackVM) {
		vm2.Push(ccc)
		vm2.Push(len(ccc))
	})
}

func Rote(vm forth.StackVM) {
	x3 := vm.Pop()
	x2 := vm.Pop()
	x1 := vm.Pop()
	vm.Push(x2)
	vm.Push(x3)
	vm.Push(x1)
}

func Evaluate(vm forth.StackVM) {
	s := vm.Pop().(string)
	in := bytes.NewBufferString(s)
	vm.SetInput(in)
	defer vm.ResetInput()
	defer func() {
		r := recover()
		if r == "EOF" {
			// ok
		} else {
			panic(r)
		}
	}()
	for {
		Interpret(vm)
	}
}

func If(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined")
	}
	orig := vm.DataSpace().Reserved()
	vm.Append(0) // placeholder
	vm.ControlStack().Push(orig)
	vm.Append(func(vm2 forth.StackVM) {
		next := vm2.Pop().(int)
		if x := vm2.Pop(); x == vFalse {
			vm2.Goto(next)
		}
	})
}

func Else(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined")
	}
	orig1 := vm.ControlStack().Pop().(int)
	// place a reference for THEN to update
	orig2 := vm.DataSpace().Reserved()
	vm.Append(0)
	vm.ControlStack().Push(orig2)
	// point true path to THEN
	vm.Append(func(vm2 forth.StackVM) {
		vm2.Goto(vm.Pop().(int))
	})
	// point false condition to after-ELSE
	vm.DataSpace().Store(orig1, vm.DataSpace().Reserved())
}

func Then(vm forth.StackVM) {
	vm.Push(0) // STATE
	Fetch(vm)
	if vm.Pop() == vFalse {
		panic("undefined")
	}
	orig := vm.ControlStack().Pop().(int)
	next := vm.DataSpace().Reserved()
	vm.DataSpace().Store(orig, next)
}

func Swap(vm forth.StackVM) {
	x2 := vm.Pop()
	x1 := vm.Pop()
	vm.Push(x2)
	vm.Push(x1)
}

func Exit(vm forth.StackVM) {
	vm.Goto(int(vm.ReturnStack().Pop().(forth.NestSys)))
}

func Count(vm forth.StackVM) {
	s := vm.Pop().(string)
	vm.Push(s)
	vm.Push(len(s))
}

func QuestionDupe(vm forth.StackVM) {
	x := vm.Pop()
	if x == 0 {
		vm.Push(0)
	} else {
		vm.Push(x)
		vm.Push(x)
	}
}
